<?php

/* ========================================================================= */
/* This file is part of the HypnoPHP Framework Library                       */
/* Copyright (c) 2009-2010, Radu Buzea                                       */
/* ========================================================================= */

/**
* @author Radu Buzea
* @copyright Copyright (c) 2009-2010, Radu Buzea (www.hypnosis.ro)
* @license GNU Lesser General Public License (www.gnu.org/copyleft/lesser.html)
* @package HypnoPHP
* @category Core
* @version 1.0.3
*/
// ============================================================================
// Interfaces: IBaseDbTable
// ============================================================================

interface IBaseDbTable
{

	public function isReady();
	public function rowCount();
	public function colCount();
	public function getRow();
	public function getRows();
	public function getCols($refresh_list = false);
	public function getColObject($col_name);
	public function assignCol($col);
	public function colExists($col_name);
	public function contentQuery($sql);
	public function freeResults($clear_data = true);

	public function initSQL();
	public function find();
	public function select($fields, $where = null, $limit = null);
	public function update(array $fields_data, $where, $limit = null);
	public function insert(array $fields_data);
	public function delete($where, $limit = null);
	public function optimize();
	public function drop();
	public function clear();

	public function generateCacheStructure($table_id = '');
	public function writeCacheStructure();
	public function readCachedStructure();
}

// ============================================================================
// Abstract class: HBaseDbTable
// ============================================================================

abstract class HBaseDbTable extends HBaseObject implements IBaseDbTable
{

	protected $__ready_state = 0;
	protected $__database_object = null;
	protected $__database_result = null;

	protected $__rows = array();
	protected $__cols = array();
	protected $__fields = array();
	protected $__limit_start = 0;
	protected $__limit_length = 30;
	protected $__sql = '';
	protected $__cache_structure = false;

	const DEFAULT_TABLE_SELECT_LIMIT = 30;

	# ###########################################################################

	/**
	* Class construct
	*
	* @return HBaseDbTable
	*/
	public function __construct()
	{
		$this->__database_result = null;
		$this->__database_object = null;
		$this->__ready_state = 0;
		$this->table_name = '';
		$this->schema = '';

		$db_object = null;
		$use_cache = false;
		$write_cache_on_init = false;

		$func_arguments = func_get_args();
		if (isset($func_arguments[0])) { $func_argument1 = $func_arguments[0]; }
		else { $func_argument1 = false; }
		if (isset($func_arguments[1])) { $func_argument2 = $func_arguments[1]; }
		else { $func_argument2 = false; }

		if (is_string($func_argument1)) {
			if (strpos($func_argument1, '.') !== false) {
				$params = preg_split("/[.]/", $func_argument1);
				if (isset($params[0])) { $this->schema = trim($params[0]); }
				if (isset($params[1])) { $this->table_name = trim($params[1]); }
			} else {
				$this->table_name = trim($func_argument1);
			}
			$use_cache = $this->__cache_structure = boolval($func_argument2);
		} else if (is_array($func_argument1)) {
			foreach ($func_argument1 as $key => $value) {
				$key = strtolower($key);
				switch ($key) {
					case 'cache':
					case 'cache_structure':
						$use_cache = $this->__cache_structure = boolval($value);
						break;
					case 'table':
					case 'table_name':
						if (!$use_cache) { $this->table_name = trim($value); }
						break;
					case 'schema':
					case 'schema_name':
						if (!$use_cache) { $this->schema = trim($value); }
						break;
					case 'database':
					case 'database_object':
						if (is_object($value)) { $db_object = $this->__database_object = $value; }
						break;
					case 'fields':
						if (!$use_cache) {
							if (is_string($value)) {
								$list = preg_split("/[\s,;\.]+/", $value);
								if (is_array($list) && count($list) > 0) { $value = $list; }
								else { $value = array($value); }
							}
							if (is_array($value) && count($value) > 0) {
								$this->__init_fields = $value;
							}
						}
						break;
					case 'limit_start':
						$this->__limit_start = intval($value);
						break;
					case 'limit_length':
						$this->__limit_length = intval($value);
						break;
					case 'sql':
						$sql = $value;
						break;
					default:
						$this->$key = $value;
				}
			}
		}
		if ($this->__database_object == null) {
			$db_object = $this->__database_object = HApplication::getDatabase();
		}

		parent::__construct();

		if (isset($sql) && is_string($sql)) {
			$this->__sql = $sql;
		} else if (isset($sql) && is_object($sql) && method_exists($sql, 'execute')) {
			$this->__sql = $sql->execute();
		} else if (is_object($db_object)) {
			$this->initSQL();
		}
		if ($use_cache) {
			if (!$this->readCachedStructure()) {
				$this->getCols();
				$this->writeCacheStructure();
			}
		} else {
			$this->getCols();
		}
		if ($this->__sql != '') { $this->getRows(); }
	}

	# ###########################################################################

	/**
	* Class destructor
	*
	* @return void
	*/
	public function __destruct()
	{
		if ($this->__cache_structure) { $this->writeCacheStructure(); }
		parent::__destruct();
	}

	# ###########################################################################

	/**
	* Database object property getter method
	*
	* @return object (HBaseDb) or null
	*/
	protected function __get_database_object()
	{
		return $this->__database_object;
	}

	# ###########################################################################

	/**
	* Database object property setter method
	*
	* @param object $param HBaseDb
	* @return void
	*/
	protected function __set_database_object($param = null)
	{
		if (is_null($param)) {
			$this->__ready_state = 0;
		} else if (is_object($param) && is_subclass_of($param, 'HBaseDb')) {
			$this->__database_object = $param;
			$this->__ready_state = 0;
		}
	}

	# ###########################################################################

	/**
	* Get the database table object state
	*
	*	@return bool
	*/
	public function isReady()
	{
		if ($this->__ready_state > 0) { return ($this->__ready_state == 1); }
		$result = true;
		if (is_null($this->__database_object) || !is_object($this->__database_object) ||
			!is_subclass_of($this->__database_object, 'HBaseDb')) { $result = false; }
		$this->__ready_state = $result ? 1 : 2;
		return $result;
	}

	# ###########################################################################

	/**
	* Get number of rows in the database table
	*
	*	@return integer
	*/
	public function rowCount()
	{
		$a = mysql_numrows();
		if (count($this->__rows) == 0) { $rows = $this->getRows(); }
		else { $rows = $this->__rows; }
		if (!is_array($rows)) { return 0; }
		return count($rows);
	}

	# ###########################################################################

	/**
	* Get number of columns (fields) in the database table
	*
	*	@return integer
	*/
	public function colCount()
	{
		if (count($this->__cols) == 0) { $cols = $this->getCols(); }
		else { $cols = $this->__cols; }
		if (!is_array($cols)) { return 0; }
		return count($cols);
	}

	# ###########################################################################

	/**
	* Get current row data form the database table
	*
	*	@return resource
	*/
	public function getRow()
	{
		if (!$this->isReady()) { return null; }
		if (empty($this->__database_result)) {
			$this->contentQuery($this->sql);
			if (empty($this->__database_result)) { return null; }
		}
		return $this->__database_object->fetchAssoc($this->__database_result);
	}

	# ###########################################################################

	/**
	* Get rows data form the database table
	*
	*	@return array
	*/
	public function getRows()
	{
		$this->__rows = array();
		while ($row_result = $this->getRow()) {
			$this->__rows[] = $row_result;
		}
		return $this->__rows;
	}

	# ###########################################################################

	/**
	* Get a list of columns from the database table
	*
	*	@return array or null
	*/
	public function getCols($refresh_list = false)
	{
		if (!$this->isReady()) { return null; }
		if (!$refresh_list && is_array($this->__cols) && count($this->__cols)) {
			return $this->__cols;
		}
		if (method_exists($this, 'list_cols')) { $this->__cols = $this->list_cols(); }
		return $this->__cols;
	}

	# ###########################################################################

	/**
	* Get a column object
	*
	* @param string $col_name
	*	@return HBaseDbField or null
	*/
	public function getColObject($col_name)
	{
		if (!is_string($col_name) || trim($col_name) == '') { return null; }
		if (!is_array($this->__cols) || count($this->__cols) == 0) {
			$cols = $this->getCols();
			if (!is_array($cols) || count($cols) == 0) { return null; }
		} else {
			$cols = $this->__cols;
		}
		$col_name = trim($col_name);
		if (isset($cols[$col_name]) && is_object($cols[$col_name]) &&
			is_subclass_of($cols[$col_name], 'HBaseDbField')) { return $cols[$col_name]; }
		return false;
	}

	# ###########################################################################

	/**
	* Add a column object in the columns list
	*
	* @param HBaseDbField $col
	* @return array (columns list) or false on error
	*/
	public function assignCol($col)
	{
		if (!is_object($col) || !is_subclass_of($col, 'HBaseDbField')) {
			return false;
		}
		$field_name = $col->name;
		$this->__cols[$field_name] = $col;
		return $this->__cols;
	}

	# ###########################################################################

	/**
	* Check if a column exists
	*
	* @param string $col_name
	* @return bool
	*/
	public function colExists($col_name)
	{
		if (!is_string($col_name) || trim($col_name) == '') { return false; }
		return (is_array($this->__cols) && isset($this->__cols[trim($col_name)]));
	}

	# ###########################################################################

	/**
	* Excecute SQL SELECT query to get the table content
	*
	* @param string or HBaseDbQuery $sql
	* @return resource (database result) or null on error
	*/
	public function contentQuery($sql)
	{
		if (!$this->isReady()) { return null; }
		if (is_object($sql) && is_subclass_of($sql, 'HBaseDbQuery')) {
			$sql = $sql->execute();
		}
		$db_object = $this->__database_object;
		if (!is_object($db_object)) { return null; }
		if (!is_string($sql) || trim($sql) == '') {
			$table_name = $this->table_name;
			if (is_string($this->__sql) && trim($this->__sql) != '') {
				$sql = $this->__sql;
			} else if (method_exists($this, 'default_select_sql')){
				$sql = $this->default_select_sql();
			} else if (!is_null($table_name) && trim($table_name) != '') {
				$table_name = trim($table_name);
				$sql = "SELECT * FROM " . $db_object->escapeNames($table_name);
			} else {
				return null;
			}
		}
		$this->freeResults(true);
		return $this->__database_result = $db_object->query($sql);
	}

	# ###########################################################################

	/**
	* Free database results and optionaly clear the internal object data
	*
	* @param bool $clear_data Clear internal object data
	* @return void
	*/
	public function freeResults($clear_data = true)
	{
		if (!$this->isReady()) { return null; }
		if (is_int($this->__database_result) && $this->__database_result > 0) {
			$this->__database_object->freeResult($this->__database_result);
		}
		$this->__database_result = null;
		if ($clear_data) { $this->__rows = array(); }
	}

	# ###########################################################################

	/**
	* Get an array with columns, rows, columns number, and rows number
	*
	* @return array
	*/
	public function getColsRows()
	{
		$rows = $this->getRows();
		$cols = $this->getCols();
		return array(
			'cols' => $cols,
			'rows' => $rows,
			'col_count' => count($cols),
			'row_count' => count($rows),
		);
	}

	# ###########################################################################

	/**
	* SQL property getter method
	*
	* @return string
	*/
	public function __get_sql()
	{
		return trim($this->__sql);
	}

	# ###########################################################################

	/**
	* SQL property setter method
	*
	* @param string $value The SQL code
	* @return void
	*/
	public function __set_sql($value)
	{
		if (is_string($value)) {
			$this->__sql = $value;
		} else if (is_object($value) && method_exists($value, 'execute')) {
			$this->__sql = $value->execute();
		} else {
			return;
		}
		if (trim($this->__sql) != '') {
			$this->contentQuery($this->__sql);
		}
	}

	# ###########################################################################

	/**
	* Cache structure property getter method
	*
	* @return bool
	*/
	public function __get_cache_stucture()
	{
		return $this->__cache_stucture;
	}

	# ###########################################################################

	/**
	* Cache structure property setter method
	*
	* @param bool $value
	* @return void
	*/
	public function __set_cache_stucture($value = false)
	{
		$this->__cache_stucture = boolval($value);
		if ($this->__cache_stucture) {
			$this->readCachedStructure();
		}
	}

	# ###########################################################################

	/**
	* Global members overloading - Getter method
	*
	* @param string $param The object member name
	*/
	public function __get($param = '')
	{
		$cols = $this->__cols;
		if (is_array($cols) && isset($cols[$param])) {
			$row = $this->getRow();
			if (isset($row[$param])) { return $row[$param]; }
		}
		return parent::__get($param);
	}

	# ###########################################################################

	/**
	* Global members overloading - Setter method
	*
	* @param string $param The object member name
	* @param mixed $value The object member value
	*/
	public function __set($param = '', $value = null)
	{
		return parent::__set($param, $value);
	}

	# ###########################################################################

	/**
	* Generate the PHP code of the table structure
	*
	* @param string $table_id
	* @return string
	*/
	public function generateCacheStructure($table_id = '')
	{
		if (!$this->__checkVarName($table_id)) { $table_id = md5($this->table_name); }
		$cols = $this->getCols();
		$table_class = get_class($this);
		if (!is_array($cols) || count($cols) == 0) {
			$cols = $this->listFields();
			if (!is_array($cols) || count($cols) == 0) { return false; }
		}

		$data = "<" . "?php\n\n/* \n" .
			" This file was generated by the HypnoPHP Framework Library\n" .
			" ===========================================================================\n" .
			" Generated in $table_class class (extende from " . __CLASS__ . " class)\n" .
			" Date and time: " . date('l, F j, Y \a\t H:i:s') . "\n" .
			" Database table name: " . $this->table_name . "\n" .
			"*/\n\n" .
			"function table_cached_structure_$table_id(\$tblobj = null)\n{\n" .
			"\tif (!is_object(\$tblobj) || !is_subclass_of(\$tblobj, \"" . __CLASS__ . "\")) { return false; }\n\n";

		foreach ($cols as $col) {
			$data .= "\t" . trim($col->generateCahceCode()) . "\n";
			$data .= "\t\$tblobj->assignCol(\$col);\n\n";
		}

		$value = $this->table_name;
		if ($value != '') { $data .= "\t\$tblobj->table_name = \"" . addslashes($value) . "\";\n"; }
		$value = $this->schema;
		if ($value != '') { $data .= "\t\$tblobj->schema = \"" . addslashes($value) . "\";\n"; }
		$value = $this->sql;
		if ($value != '') { $data .= "\t\$tblobj->sql = \"" . addslashes($value) . "\";\n"; }

		$data .= "\treturn true;\n}\n";
		return $data;
	}

	# ###########################################################################

	/**
	* Write the database table object columns structure into a cache file
	*
	* @param bool $recache Rewrite the existing cache file (if any)
	* @return bool
	*/
	public function writeCacheStructure($recache = false)
	{
		if (trim($this->table_name) == '') {
			return false;
		}
		$add_dbname = HApplication::getConfig('add_dbname_in_cache_filename');
		if ($add_dbname === true) {
			$tbl_id = preg_replace('/\W/', '_', $this->__database_object->db_name . '_' . $this->table_name);
		} else {
			$tbl_id = preg_replace('/\W/', '_', $this->table_name);
		}
		global $_HAPPLICATION_PATHS;
		if (isset($_HAPPLICATION_PATHS['app_cache'])) {
			$cache_path = $_HAPPLICATION_PATHS['app_cache'];
		} else if (defined('HAPPLICATION_PATH')) {
			$cache_path = defined('HAPPLICATION_PATH') . DS . 'cache';
		}
		$file_name = $cache_path . DS . "dbtstruct_$tbl_id.php";
		$this->cache_file_name = $file_name;
		if (is_file($file_name)) {
			if ($recache) { unlink($file_name); }
			else { return true; }
		}
		$data = $this->generateCacheStructure($tbl_id);
		if (trim($data) != '') {
			return HFileSys::writeFile($data, $file_name);
		}
		return false;
	}

	# ###########################################################################

	/**
	* Read the database table object columns structure from a cache file
	*
	* @return bool
	*/
	public function readCachedStructure()
	{
		if (trim($this->table_name) == '') {
			return false;
		}
		$add_dbname = HApplication::getConfig('add_dbname_in_cache_filename');
		if ($add_dbname === true) {
			$tbl_id = preg_replace('/\W/', '_', $this->__database_object->db_name . '_' . $this->table_name);
		} else {
			$tbl_id = preg_replace('/\W/', '_', $this->table_name);
		}
		$function_name = "table_cached_structure_{$tbl_id}";
		if (function_exists($function_name)) {
			return call_user_func($function_name, $this);
		}
		global $_HAPPLICATION_PATHS;
		if (isset($_HAPPLICATION_PATHS['app_cache'])) {
			$cache_path = $_HAPPLICATION_PATHS['app_cache'];
		} else if (defined('HAPPLICATION_PATH')) {
			$cache_path = defined('HAPPLICATION_PATH') . DS . 'cache';
		}
		if (!isset($cache_path)) { return false; }
		$file_name = $cache_path . DS . "dbtstruct_{$tbl_id}.php";
		$this->cache_file_name = $file_name;
		if (is_file($file_name)) {
			include $file_name;
			if (function_exists($function_name)) {
				return call_user_func($function_name, $this);
			}
		}
		return false;
	}

	# ###########################################################################

}
